你可以这样复用AlertDialog

AlertDialog复用探讨

作者:李旺成
时间:2016年4月16日


前一阵在重构公司项目时,看到项目中用到了很多 Dialog,当时就在想,这么多的 Dialog,那能不能复用一下?
这里先介绍我是如何复用 AlertDialog 的,关于自定义 Dialog 的复用,以后有机会在写一篇。

为什么要复用

这就好比问:为什么要使用单例?使用单例有什么好处?

好吧!啰嗦两句:单例可以节省不必要的内存开销,屏蔽对象创建的复杂性。

对!这里讨论为什么要复用 AlertDialog;就是借鉴了单例设计模式,为了减少对象的创建,节省内存的开销。Java 代码优化里面有一条很基本的 —— 尽量少创建对象。

注意:如果在你的应用当中很少使用到 AlertDialog,那么就可以不同考虑这个问题了。

关于 AlertDialog 的复用我觉得可以根据 AlertDialog 在 App 中实例的数量,分为两种情况:

  • 整个 App 中单例
  • 每个 Activity 中单例

下面就这两种情况分别展开介绍。

整个 App 中单例

也就是说在整个 App 中都是唯一的,先看看实现的效果:

App 全局唯一 AlertDialog(一)

App 全局唯一 AlertDialog(二)

如上图所示,在 MainActivity 与 SecondActivity 中所显示的这六个 AlertDialog 都是同一个实例。

简介

使用单例模式,那么就可以保证在整个项目(也就是 App)中都只存在一个实例对象。

我的实现思路是这样的,使用 Application 的 Activity 来创建 AlertDialog,然后使用单例模式,保证该 AlertDialog 的全局唯一性。

要解决的问题

简单实现

代码很简单,直接看看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class AppAlertDialogManager {

private static AlertDialog sAlertDialog;

public static AlertDialog displayOneBtnDialog(String title, String msg) {
if (sAlertDialog != null) {
sAlertDialog.setTitle(title);
sAlertDialog.setMessage(msg);
} else {
sAlertDialog = new AlertDialog.Builder(BaseApp.getInstance())
.setTitle(title)
.setMessage(msg)
.setNegativeButton("取消", null)
.setPositiveButton("确定", null)
.create();
sAlertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
}
return sAlertDialog;
}

}

如何使用:

1
2
3
4
5
6
private void showAppSingleAlertDialog(String title, String msg) {
AlertDialog alertDialog = AppAlertDialogManager.displayOneBtnDialog(title, msg);
mADAddressTV.setText(alertDialog.toString());
Log.e(TAG, alertDialog.toString());
alertDialog.show();
}

这里只列出简要代码,请不要计较说这不是单例(只是演示一下),很多场景都没有覆盖到,例如:要显示按钮数量不同的 AlertDialog 该如何处理。

注意:因为,我不打算推荐这种方式,所以,也就没有将这些情况在这里实现了,这些问题将会在下面的实现方案中给出解决方案。

每个 Activity 中单例

意思就是,如果使用了 AlertDialog,那么在该 Activity 中有且仅会有一个 AlertDialog 实例,看效果图:

MainActivity 中唯一 AlertDialog

SecondActivity 中唯一 AlertDialog

如上图所示,在 MainActivity 中所显示的三个 AlertDialog 与 SecondActivity 中所显示的三个 AlertDialog ,在各自的页面下都是同一个实例;但是对比这两个 Activity 中的 AlertDialog 的地址时,发现它们并不相同,也就是说,不同页面中的 AlertDialog 不是同一个实例。

简介

这并不是单例模式那种全局唯一,只保证在当前的 Activity 中只有一个实例,也就是在当前 Activity 中可以实现复用。

我的实现思路是这样的:

Activity 中 AlertDialog 复用实现流程

首先,在一个工具类(ActivityAlertDialogManager.java)中,使用静态变量保存 AlertDialog 的实例,这与单例中使用静态变量保存单例类实例的作用一致。

然后,在每次需要显示 AlertDialog 的时候,根据工具类中保存的 AlertDialog 实例是否为空,来决定是否要创建新的 AlertDialog。如果,AlertDialog 不为空,则需要判断 Activity 是否切换。

最后,如果 Activity 切换了,那么就需要创建新的 Builder,否则可以尝试复用 Builder,然后就可以显示了。

要解决的问题

1、Buidler 的复用
AlertDialog 是通过 Buidler 来创建的,那么也需要考虑 Builder 复用的问题。所以,我也使用静态变量保留了 Builder 的实例。

解决了 Builder 复用的问题就可以复用 AlertDialog?使用一个 Builder 对象的 show() 方法,显示的是同一个 AlertDialog?

先来看下 Builder.show() 方法:

1
2
3
4
5
public AlertDialog show() {
final AlertDialog dialog = create();
dialog.show();
return dialog;
}

坑!有没有,我当时只考虑了 Builder 的复用,然后,直接使用 Builder 实例 show() 出来。结果可想而知,每次显示的 AlertDialog 都是新的实例

2、AlertDialog Button 如何复用
AlertDialog 的 setTitle() 和 setMessage() 方法每次调用后都可以生效,但是 setButton() 方法却有点问题,设置过一遍之后,以后不管怎么设置都没有效果。

关于这个问题,在我尝试使用 AlertDialog 的 setButton() 方法没有起到想要的效果,以及没有找到办法修改已有 AlertDialog 的 Buidler (我当时以为多次调用 Builder 的 setXxxButton() 方法有用,所以尝试了一下)之后,我就没有再在这两个方法上找思路了。

setButton() 既然行不通,那好,AlertDialog 也是个 Dialog,那么,它应该也可以管理自己的 View。对,就是拿到 AlertDialog 的 Button,然后,给它们重新赋值,或者说修改其属性等。

3、static 变量引用 Activity
Android 编程中有一个很容易导致内存泄漏的问题就是使用 static 变量引用 Activity。static 变量的生命周期很长,使用 static 引用 Activity,很容易导致 Activity 无法释放。

这里采用的方法是在 Activity 的 onPause() 方法中,清空 ActivityAlertDialogManager 中对 Activity 的引用。

简单实现

这里给出了一个简易的实现,只是考虑的一个按钮和两个按钮 AlertDialog 复用时的一些处理。权当参考,以思路为主,看代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
public class ActivityAlertDialogManager {

//==========常量==========
private static final String TAG = "ActivityADManager";

//==========普通静态变量==========
private static AlertDialog sAlertDialog; // 一个Activity下只产生一个AlertDialog实例
private static AlertDialog.Builder sBuilder; // 一个Activity下只产生一个AlertDialog.Builder实例
private static Activity sLastActivity = null;

//==========AlertDialog==========//
public static AlertDialog displayOneBtnDialog(@NonNull Activity Activity, String title, String msg) {
if (TextUtils.isEmpty(msg)) return null;
if (sAlertDialog == null) {
TipInfo tipInfo = TipInfo.createTipInfo(title, msg);
sAlertDialog = displayOneBtnDialog(Activity, tipInfo, null);
} else {
sAlertDialog.setTitle(title);
sAlertDialog.setMessage(msg);
DialogInterface.OnClickListener listener = null;
sAlertDialog.setButton(DialogInterface.BUTTON_NEGATIVE, "取消", listener);
sAlertDialog.getButton(DialogInterface.BUTTON_NEGATIVE).setVisibility(View.GONE);
}
return sAlertDialog;
}

public static AlertDialog displayOneBtnDialog(@NonNull Activity Activity, TipInfo tipInfo, DialogInterface.OnClickListener sureListener) {
if (tipInfo == null) return null;
AlertDialog.Builder builder = getBuilder(Activity, tipInfo);
builder = addAlertDialogPositiveButton(tipInfo.sureBtnText, sureListener, builder);
// 显示出该对话框
sAlertDialog = builder.create();
DialogInterface.OnClickListener listener = null;
sAlertDialog.setButton(DialogInterface.BUTTON_NEGATIVE, "", listener);
if (sAlertDialog.getButton(DialogInterface.BUTTON_NEGATIVE) != null) {
sAlertDialog.getButton(DialogInterface.BUTTON_NEGATIVE).setVisibility(View.GONE);
}
return sAlertDialog;
}

public static AlertDialog displayTwoBtnDialog(@NonNull Activity Activity, String title, String msg) {
if (TextUtils.isEmpty(msg)) return null;
if (sAlertDialog == null) {
TipInfo tipInfo = TipInfo.createTipInfo(title, msg);
sAlertDialog = displayTwoBtnDialog(Activity, tipInfo, null, null);
} else {
sAlertDialog.setTitle(title);
sAlertDialog.setMessage(msg);
if (sAlertDialog.getButton(DialogInterface.BUTTON_NEGATIVE) != null) {
sAlertDialog.getButton(DialogInterface.BUTTON_NEGATIVE).setText("取消");
sAlertDialog.getButton(DialogInterface.BUTTON_NEGATIVE).setVisibility(View.VISIBLE);
}
}
return sAlertDialog;
}

public static AlertDialog displayTwoBtnDialog(@NonNull Activity Activity, TipInfo tipInfo, DialogInterface.OnClickListener cancelListener, DialogInterface.OnClickListener sureListener) {
if (tipInfo == null) return null;
// 通过AlertDialog.Builder这个类来实例化我们的一个AlertDialog的对象
AlertDialog.Builder builder = getBuilder(Activity, tipInfo);
builder = addAlertDialogPositiveButton(tipInfo.sureBtnText, sureListener, builder);
builder = addAlertDialogNegativeButton(tipInfo.cancelBtnText, cancelListener, builder);
// 显示出该对话框
sAlertDialog = builder.show();
return sAlertDialog;
}

@NonNull
private static AlertDialog.Builder getBuilder(@NonNull Activity Activity, TipInfo tipInfo) {
AlertDialog.Builder builder;
if (Activity == sLastActivity) {
if (sBuilder != null) {
builder = sBuilder;
} else {
builder = createNewBuilder(Activity);
}
} else {
reset();
builder = createNewBuilder(Activity);
sLastActivity = Activity;
sBuilder = builder;
}
// 通过AlertDialog.Builder这个类来实例化我们的一个AlertDialog的对象
// 设置Title的图标
builder.setIcon(tipInfo.iconResId);
// 设置Title的内容
builder.setTitle(tipInfo.title);
// 设置Content来显示一个信息
builder.setMessage(tipInfo.msg);
return builder;
}

private static void reset() {
sBuilder = null;
sAlertDialog = null;
sLastActivity = null;
}

@NonNull
private static AlertDialog.Builder createNewBuilder(@NonNull Activity Activity) {
AlertDialog.Builder builder;
builder = new AlertDialog.Builder(Activity);
sBuilder = builder;
return builder;
}

private static AlertDialog.Builder addAlertDialogPositiveButton(String btnText, DialogInterface.OnClickListener listener, final AlertDialog.Builder builder) {
listener = getDefaultOnClickListener(listener);
// 设置一个PositiveButton
builder.setPositiveButton(btnText, listener);
return builder;
}

private static AlertDialog.Builder addAlertDialogNegativeButton(String btnText, DialogInterface.OnClickListener listener, final AlertDialog.Builder builder) {
listener = getDefaultOnClickListener(listener);
// 设置一个PositiveButton
builder.setNegativeButton(btnText, listener);
return builder;
}

@NonNull
private static DialogInterface.OnClickListener getDefaultOnClickListener(DialogInterface.OnClickListener listener) {
if (listener != null) return listener;
listener = new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Log.e(TAG, "AlertDialog Button Click!");
}
};
return listener;
}
//==========AlertDialog==========//


//==========逻辑方法==========//
public static void destory(@NonNull Activity Activity) {
if (Activity != sLastActivity) {
Activity = null;
return;
}
if (sAlertDialog != null) {
sAlertDialog.cancel();
}
Activity = null;
reset();
}

}

思路在上面已经简单说过,而且代码里面基本都有注释,这里就不多做解释了。

小结

两种方式对比

上面说过,我建议使用“每个 Activity 中单例”的实现方案,但是“整个 App 中单例”也有其优势,这里简单介绍下:

使用 Application Activity 的优缺点

优点:整个 App 中都只有一个 AlertDialog 的实例,也就是单例的优点,最少的实例
缺点:
1、需要申请多余的权限,需要更改 Window 的 type,更坑的是显示与否是不确定的

在 MIUI 系统中,可以关闭 App 显示悬浮窗的权限,如果该权限被关闭了,那么你会发现,全局 AlertDialog 怎么都显示不出来,关键还不会报错。这里,我考虑过先确认是否打开了悬浮窗权限,然后给出相应提示,引导用户开启该权限。并不顺利,放弃了,这是不建议使用该方式的最主要原因。

MIUI 中设置悬浮窗权限

2、返回桌面“失效”
先看效果:

返回桌面失效

你可以试一下,展示全局对话框,然后点回到桌面按键,你会发现该对话框还存在,并没有和你的 App 一起隐藏。

当然,这个问题可以解决,监听回到桌面的广播,然后去处理一下就可以了;但是,这不是麻烦了不是。

使用 Activity Activity 的优缺点

优点:相比 Application Activity 而言,没有它的那些缺点
缺点:要注意避免 Activity 的内存泄漏

##写在最后的话##
不足:这里主要是想探讨一下思路,代码很简略,肯定还有很多不足的地方,希望大家多提建议,集思广益。

就拿权限申请来说,其实有不需要权限的显示全局悬浮窗(注意这里没有说是 AlertDialog)的方案,有人分析过 UC 是如何不申请权限就可以实现展示悬浮窗的(),但是这不是这篇文章该讨论的了。

解决问题的思路:多思考,思维不要僵化;思路很重要,要是感觉行不通,不要一条路走到黑,相信条条大路通罗马。

项目地址

GitHub

附上动图

AlertDialog 复用演示

坚持原创技术分享,您的支持将鼓励我继续创作!